import { Cache } from './Cache.js';
import { Loader } from './Loader.js';

const loading = {};

class FileLoader extends Loader {
  constructor(manager) {
    super(manager);
  }

  load(url, onLoad, onProgress, onError) {
    if (url === undefined) url = '';

    if (this.path !== undefined) url = this.path + url;

    url = this.manager.resolveURL(url);

    const scope = this;

    const cached = Cache.get(url);

    if (cached !== undefined) {
      scope.manager.itemStart(url);

      setTimeout(() => {
        if (onLoad) onLoad(cached);

        scope.manager.itemEnd(url);
      }, 0);

      return cached;
    }

    // Check if request is duplicate

    if (loading[url] !== undefined) {
      loading[url].push({
        onLoad,
        onProgress,
        onError,
      });

      return;
    }

    // Check for data: URI
    const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
    const dataUriRegexResult = url.match(dataUriRegex);
    let request;

    // Safari can not handle Data URIs through XMLHttpRequest so process manually
    if (dataUriRegexResult) {
      const mimeType = dataUriRegexResult[1];
      const isBase64 = !!dataUriRegexResult[2];

      let data = dataUriRegexResult[3];
      data = decodeURIComponent(data);

      if (isBase64) data = atob(data);

      try {
        let response;
        const responseType = (this.responseType || '').toLowerCase();

        switch (responseType) {
          case 'arraybuffer':
          case 'blob':
            const view = new Uint8Array(data.length);

            for (let i = 0; i < data.length; i++) {
              view[i] = data.charCodeAt(i);
            }

            if (responseType === 'blob') {
              response = new Blob([view.buffer], { type: mimeType });
            } else {
              response = view.buffer;
            }

            break;

          case 'document':
            const parser = new DOMParser();
            response = parser.parseFromString(data, mimeType);

            break;

          case 'json':
            response = JSON.parse(data);

            break;

          default:
            // 'text' or other

            response = data;

            break;
        }

        // Wait for next browser tick like standard XMLHttpRequest event dispatching does
        setTimeout(() => {
          if (onLoad) onLoad(response);

          scope.manager.itemEnd(url);
        }, 0);
      } catch (error) {
        // Wait for next browser tick like standard XMLHttpRequest event dispatching does
        setTimeout(() => {
          if (onError) onError(error);

          scope.manager.itemError(url);
          scope.manager.itemEnd(url);
        }, 0);
      }
    } else {
      // Initialise array for duplicate requests

      loading[url] = [];

      loading[url].push({
        onLoad,
        onProgress,
        onError,
      });

      request = new XMLHttpRequest();

      request.open('GET', url, true);

      request.addEventListener(
        'load',
        function (event) {
          const response = this.response;

          const callbacks = loading[url];

          delete loading[url];

          if (this.status === 200 || this.status === 0) {
            // Some browsers return HTTP Status 0 when using non-http protocol
            // e.g. 'file://' or 'data://'. Handle as success.

            if (this.status === 0) console.warn('THREE.FileLoader: HTTP Status 0 received.');

            // Add to cache only on HTTP success, so that we do not cache
            // error response bodies as proper responses to requests.
            Cache.add(url, response);

            for (let i = 0, il = callbacks.length; i < il; i++) {
              const callback = callbacks[i];
              if (callback.onLoad) callback.onLoad(response);
            }

            scope.manager.itemEnd(url);
          } else {
            for (let i = 0, il = callbacks.length; i < il; i++) {
              const callback = callbacks[i];
              if (callback.onError) callback.onError(event);
            }

            scope.manager.itemError(url);
            scope.manager.itemEnd(url);
          }
        },
        false,
      );

      request.addEventListener(
        'progress',
        event => {
          const callbacks = loading[url];

          for (let i = 0, il = callbacks.length; i < il; i++) {
            const callback = callbacks[i];
            if (callback.onProgress) callback.onProgress(event);
          }
        },
        false,
      );

      request.addEventListener(
        'error',
        event => {
          const callbacks = loading[url];

          delete loading[url];

          for (let i = 0, il = callbacks.length; i < il; i++) {
            const callback = callbacks[i];
            if (callback.onError) callback.onError(event);
          }

          scope.manager.itemError(url);
          scope.manager.itemEnd(url);
        },
        false,
      );

      request.addEventListener(
        'abort',
        event => {
          const callbacks = loading[url];

          delete loading[url];

          for (let i = 0, il = callbacks.length; i < il; i++) {
            const callback = callbacks[i];
            if (callback.onError) callback.onError(event);
          }

          scope.manager.itemError(url);
          scope.manager.itemEnd(url);
        },
        false,
      );

      if (this.responseType !== undefined) request.responseType = this.responseType;
      if (this.withCredentials !== undefined) request.withCredentials = this.withCredentials;

      if (request.overrideMimeType)
        request.overrideMimeType(this.mimeType !== undefined ? this.mimeType : 'text/plain');

      for (const header in this.requestHeader) {
        request.setRequestHeader(header, this.requestHeader[header]);
      }

      request.send(null);
    }

    scope.manager.itemStart(url);

    return request;
  }

  setResponseType(value) {
    this.responseType = value;
    return this;
  }

  setMimeType(value) {
    this.mimeType = value;
    return this;
  }
}

export { FileLoader };
